﻿namespace NTLib.Pipe.Containers
{
    using NTLib.Core.Pool;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using NTLib.Pipe.Targets;
    using NTLib.Core.Threading;
    using NTLib.Core.Tasks;
    using System.Threading;
    using System.Diagnostics;
    using NTLib.Pipe.Exceptions;
    using System.Runtime.CompilerServices;
    using NTLib.Core.Args;


    /// <summary>
    /// Pipe That will use one source to make data navigate.
    /// ThreadSafe
    /// </summary>
    /// <typeparam name="T">Data to stored</typeparam>
    public class PipeUsingSource<T> : BasePipePoolItem, IPipe<T>, IPipeOwner
    {
        #region Fields

        private readonly AsyncLock _lock;
        private readonly AsyncLock _lockPop;
        private readonly Queue<TaskCompletionSource<T>> _waiterPop;

        protected IPipeSource<T> _source;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="MemoryPipeSource{T}"/> class.
        /// </summary>
        public PipeUsingSource()
        {
            this._lock = new AsyncLock();
            this._lockPop = new AsyncLock();
            this._waiterPop = new Queue<TaskCompletionSource<T>>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the unique ID.
        /// </summary>
        public override Guid Id
        {
            get
            {
                InitializeCheck();
                return this._source.Id;
            }
        }

        public override PipeType PipeType
        {
            get
            {
                InitializeCheck();
                return base.PipeType;
            }

            protected set { base.PipeType = value; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Pop an item from the item's queue if the pipe permits it.
        /// </summary>
        /// <param name="token">Token used to cancel the task of waiting item.</param>
        /// <returns>
        /// Asynchronious task that wait there is an item in the pipe.
        /// </returns>
        public async virtual Task<T> PopAsync(CancellationToken token = default(CancellationToken))
        {
            InitializeCheck();

            TaskCompletionSource<T> waiter = new TaskCompletionSource<T>().AttachCancelToken(token);

            token.ThrowIfCancellationRequested();
            using (var lck = await this._lockPop.LockAsync(token))
            {
                this._waiterPop.Enqueue(waiter);
                token.ThrowIfCancellationRequested();
            }
            UpdatePopWaiter();
            token.ThrowIfCancellationRequested();
            return await waiter.Task;
        }

        ///// <summary>
        ///// Push an item to the item's queue if the pipe permits it and wait that the item is well arrived on the other side.
        ///// </summary>
        ///// <param name="item"></param>
        ///// <param name="token">Tpken used to cancel the task that wait to items arrived on the other side.</param>
        ///// <returns>
        ///// Asynchronious task that permit to wait that the item have been pop by the other side
        ///// </returns>
        //public async virtual Task<U> PushAsync<U>(T item, CancellationToken token = default(CancellationToken))
        //{
        //    InitializeCheck();

        //    TaskCompletionSource<object> waiter = new TaskCompletionSource<object>().AttachCancelToken(token);
        //    using (var lck = await this._lock.LockAsync(token))
        //    {
        //        await this._source.PushItemAsync<U>(item, waiter, token);
        //    }
        //    UpdatePopWaiter();
        //    return (U)await waiter.Task;
        //}
        
        /// <summary>
        /// Send an item to the item's queue if the pipe permits it.
        /// Attention this method doesn't wait that the item achieved the other side.
        /// </summary>
        /// <param name="item">Item to send</param>
        public async virtual Task SendAsync(T item, CancellationToken token = default(CancellationToken))
        {
            InitializeCheck();

            using (await this._lock.LockAsync(token))
            {
                token.ThrowIfCancellationRequested();
                await this._source.SendItemAsync(item, token);
            }
            UpdatePopWaiter();
        }

        /// <summary>
        /// Sets the source.
        /// </summary>
        public void SetSource(IPipeSource<T> source, PipeType globalType)
        {
            if (this._source != null)
                throw new PipeAlreadyInitializedException();

            this._source = source;
            this.PipeType = globalType;
            this._source.ItemDisposed += Source_ItemDisposed;
            this._source.DataReceived += UpdatePopWaiter;
            try
            {
                OnSetSource(source);
            }
            catch
            {
                this._source = null;
                throw;
            }
            this.Status = PipeStatus.Ready;
        }

        /// <summary>
        /// Called when SetSource methods is called.
        /// </summary>
        protected virtual void OnSetSource(IPipeSource<T> source)
        {
        }

        /// <summary>
        /// Called when the poolitem is reset.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        protected override void OnReset()
        {
            if (this._source != null)
                this._source.ItemDisposed -= Source_ItemDisposed;

            this._source = null;
            this.Status = PipeStatus.NotInitialize;
        }

        /// <summary>
        /// Updates the element that wait information.
        /// </summary>
        private async void UpdatePopWaiter()
        {
            try
            {
                using (var lck = await this._lock.LockAsync())
                {
                    using (var lckPop = await this._lockPop.LockAsync())
                    {
                        while (this._waiterPop.Count > 0)
                        {
                            var waiter = this._waiterPop.Peek();
                            if (waiter.Task.IsCanceled ||
                                waiter.Task.IsCompleted ||
                                waiter.Task.IsFaulted)
                            {
                                this._waiterPop.Dequeue();
                                continue;
                            }

                            var item = await this._source.DequeueItemAsync();
                            if (object.Equals(default(T), item))
                                return;

                            waiter = this._waiterPop.Dequeue();
                            waiter.SetResult(item);

                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                Debug.WriteLine(ex.StackTrace);
                // The exception could be catch after because it's a void method
            }
        }

        /// <summary>
        /// Check that the pipe is well initialized.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        protected void InitializeCheck()
        {
            if (this._source == null)
                throw new PipeNotInitializedException();
        }

        /// <summary>
        /// Source's item is disposed.
        /// </summary>
        /// <param name="obj">The object.</param>
        private void Source_ItemDisposed(object obj)
        {
            this.Dispose();
        }

        /// <summary>
        /// Manageds the dispose.
        /// </summary>
        protected override void ManagedDispose()
        {
            if (this._source != null)
                this._source.ItemDisposed -= Source_ItemDisposed;

            this._source = null;
            base.ManagedDispose();
        }

        /// <summary>
        /// Closes the pipe.
        /// </summary>
        public async Task ClosePipeAsync()
        {
            InitializeCheck();
            if (this._source != null)
            {
                this._source.Dispose();
            }
            await Task.Yield();
        }

        #endregion
    }
}
